﻿using Microsoft.Crm.Sdk.Samples.HelperCode;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using isky.webapi.requests;

namespace isky.webapi
{
    public partial class CRMWebAPI
    {
        /// <summary>
        /// Helper function to convert object to KVP
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        private static List<KeyValuePair<string, object>> ConvertObjectToKeyValuePair(object data)
        {
            if (data != null)
            {
                if (data.GetType() == typeof(ExpandoObject))
                {
                    var dataExpand = data as ExpandoObject;
                    return dataExpand.ToList();
                }
                else
                {
                    Type type = data.GetType();
                    IList<PropertyInfo> props = new List<PropertyInfo>(type.GetProperties());
                    List<KeyValuePair<string, object>> list = new List<KeyValuePair<string, object>>();

                    foreach (PropertyInfo prop in props)
                    {
                        object propValue = prop.GetValue(data, null);

                        list.Add(new KeyValuePair<string, object>(prop.Name, propValue));
                    }

                    return list;
                }
            }

            else return new List<KeyValuePair<string, object>>();
        }
        /// <summary>
        /// Helper function to build the url for functions and actions
        /// </summary>
        /// <param name="function"></param>
        /// <param name="parameters"></param>
        /// <returns></returns>
        private string BuildFunctionActionURI(string function, KeyValuePair<string, object>[] parameters)
        {
            string fullUrl;
            if (parameters != null)
            {
                List<string> paramList = new List<string>();
                List<string> valueList = new List<string>();
                int paramCount = 1;
                foreach (var parm in parameters)
                {
                    if (parm.Value.GetType() == typeof(String))
                    {
                        valueList.Add(string.Format("@p{0}='{1}'", paramCount, parm.Value));
                    }
                    else if (parm.Value.GetType().GetTypeInfo().IsPrimitive)
                    {
                        valueList.Add(string.Format("@p{0}={1}", paramCount, parm.Value));
                    }
                    else
                    {
                        valueList.Add(string.Format("@p{0}={1}", paramCount, JsonConvert.SerializeObject(parm.Value)));
                    }
                    paramList.Add(string.Format("{0}=@p{1}", parm.Key, paramCount));
                    paramCount++;
                }

                fullUrl = string.Format("{0}{1}({2})?{3}", _apiUrl, function, string.Join(",", paramList), string.Join("&", valueList));
            }
            else
            {
                fullUrl = string.Format("{0}{1}()", _apiUrl, function);
            }

            return fullUrl;
        }

        /// <summary>
        /// helper function to build query url
        /// </summary>
        /// <param name="uri"></param>
        /// <param name="queryOptions"></param>
        /// <returns></returns>
        private string BuildGetUrl(string uri, CRMGetListOptions queryOptions)
        {
            var fullurl = _apiUrl + uri;

            if ((queryOptions != null) && (!string.IsNullOrEmpty(queryOptions.TrackChangesLink)))
                fullurl = queryOptions.TrackChangesLink;

            bool firstParam = true;
            if (queryOptions != null)
            {
                if (queryOptions.Select != null)
                {

                    if (firstParam)
                        fullurl = String.Format("{0}?$select={1}", fullurl, String.Join(",", queryOptions.Select));
                    else
                        fullurl = String.Format("{0}&$select={1}", fullurl, String.Join(",", queryOptions.Select));
                    firstParam = false;

                }
                if (queryOptions.OrderBy != null)
                {

                    if (firstParam)
                        fullurl = String.Format("{0}?$orderby={1}", fullurl, String.Join(",", queryOptions.OrderBy));
                    else
                        fullurl = String.Format("{0}&$orderby={1}", fullurl, String.Join(",", queryOptions.OrderBy));
                    firstParam = false;

                }
                if (queryOptions.Filter != null)
                {
                    if (firstParam)
                        fullurl = fullurl + "?$filter=" + queryOptions.Filter;
                    else
                        fullurl = fullurl + "&$filter=" + queryOptions.Filter;
                    firstParam = false;
                }
                if (queryOptions.IncludeCount)
                {
                    if (firstParam)
                        fullurl = fullurl + "?$count=true";
                    else
                        fullurl = fullurl + "&$count=true";
                    firstParam = false;
                }

                if (queryOptions.Skip > 0)
                {
                    if (firstParam)
                        fullurl = fullurl + string.Format("?$skip={0}", queryOptions.Skip);
                    else
                        fullurl = fullurl + string.Format("&$skip={0}", queryOptions.Skip);
                    firstParam = false;
                }
                if (queryOptions.Top > 0)
                {
                    if (firstParam)
                        fullurl = fullurl + string.Format("?$top={0}", queryOptions.Top);
                    else
                        fullurl = fullurl + string.Format("&$top={0}", queryOptions.Top);
                    firstParam = false;
                }
                if (queryOptions.Expand != null)
                    BuildExpandQueryURLOptions(queryOptions, ref fullurl, ref firstParam);

                BuildAdvancedQueryURLOptions(queryOptions, ref fullurl, ref firstParam);
            }

            return fullurl;
        }

        private void BuildExpandQueryURLOptions(CRMGetListOptions queryOptions, ref string fullurl, ref bool firstParam)
        {
            List<string> expands = new List<string>();

            foreach (var expand in queryOptions.Expand)
            {
                List<string> expandOptions = new List<string>();

                if (expand.Select != null)
                    expandOptions.Add(String.Format("$select={0}", String.Join(",", expand.Select)));

                if (expand.OrderBy != null)
                    expandOptions.Add(String.Format("$orderby={0}", String.Join(",", expand.OrderBy)));

                if (expand.Filter != null)
                    expandOptions.Add("$filter=" + expand.Filter);

                if (expand.Top > 0)
                    expandOptions.Add(string.Format("$top={0}", expand.Top));

                if (expandOptions.Count > 0)
                    expands.Add(string.Format("{0}({1})", expand.Property, string.Join(";", expandOptions)));
                else
                    expands.Add(string.Format("{0}", expand.Property));

            }
            if (expands.Count > 0)
            {
                if (firstParam)
                    fullurl = fullurl + string.Format("?$expand={0}", String.Join(",", expands));
                else
                    fullurl = fullurl + string.Format("&$expand={0}", String.Join(",", expands));
                firstParam = false;
            }

        }

        private static void BuildAdvancedQueryURLOptions(CRMGetListOptions queryOptions, ref string fullurl, ref bool firstParam)
        {
            if (queryOptions.SystemQuery != Guid.Empty)
            {
                if (firstParam)
                    fullurl = fullurl + string.Format("?savedQuery={0}", queryOptions.SystemQuery.ToString());
                else
                    fullurl = fullurl + string.Format("&savedQuery={0}", queryOptions.SystemQuery.ToString());
                firstParam = false;
            }
            if (queryOptions.UserQuery != Guid.Empty)
            {
                if (firstParam)
                    fullurl = fullurl + string.Format("?userQuery={0}", queryOptions.UserQuery.ToString());
                else
                    fullurl = fullurl + string.Format("&userQuery={0}", queryOptions.UserQuery.ToString());
                firstParam = false;
            }
            if (!string.IsNullOrEmpty(queryOptions.FetchXml))
            {
                if (firstParam)
                    fullurl = fullurl + string.Format("?fetchXml={0}", Uri.EscapeUriString(queryOptions.FetchXml));
                else
                    fullurl = fullurl + string.Format("&$fetchXml={0}", Uri.EscapeUriString(queryOptions.FetchXml));
                firstParam = false;
            }
        }

        /// <summary>
        /// helper function to make sure token refresh happens as needed if refresh method provided
        /// </summary>
        private async Task<string> CheckAuthToken()
        {
            if (_getAccessToken == null)
                return _AccessToken;
            var newToken = await _getAccessToken(Authentication.DiscoverAuthority(_apiUrl), _RefreshToken);
            if (newToken != _AccessToken)
            {
                _httpClient.DefaultRequestHeaders.Remove("Authorization");
                _httpClient.DefaultRequestHeaders.Authorization =
                    new AuthenticationHeaderValue("Bearer", newToken);
                _AccessToken = newToken;
            }
            return _AccessToken;
        }
        /// <summary>
        /// helper method to setup the httpclient defaults
        /// </summary>
        /// <param name="callerID"></param>
        private void SetHttpClientDefaults(Guid callerID)
        {
            _httpClient.DefaultRequestHeaders.Add("Accept", "application/json");
            _httpClient.DefaultRequestHeaders.Add("OData-MaxVersion", "4.0");
            _httpClient.DefaultRequestHeaders.Add("OData-Version", "4.0");
            _httpClient.DefaultRequestHeaders.Add("Prefer", "odata.maxpagesize=5000");
            if (callerID != Guid.Empty)
                _httpClient.DefaultRequestHeaders.Add("MSCRMCallerID", callerID.ToString());

            _httpClient.Timeout = new TimeSpan(0, 2, 0);
        }

        /// <summary>
        /// Helper method to get ID from response
        /// </summary>
        /// <param name="fullUrl"></param>
        /// <param name="response"></param>
        /// <returns></returns>
        private static Guid GetEntityIDFromResponse(string fullUrl, HttpResponseMessage response)
        {
            if ((response == null) || (response.Headers == null))
                return Guid.Empty;
            if (!response.Headers.Contains("OData-EntityId"))
                return Guid.Empty;

            var idString = response.Headers.GetValues("OData-EntityId").FirstOrDefault();

            if (string.IsNullOrEmpty(idString))
                return Guid.Empty;

            idString = idString.Replace(fullUrl.ToLower(), "").Replace("(", "").Replace(")", "");

            var idGuid = Guid.Empty;
            //if alternate key was used to perform an upsert, guid not currently returned
            //the call returns the alternate key which is not in guid format
            Guid.TryParse(idString, out idGuid);

            return idGuid;
        }
        /// <summary>
        /// Helper method to check the response status and generate a well formatted error
        /// </summary>
        /// <param name="response"></param>
        private static void EnsureSuccessStatusCode(HttpResponseMessage response, string jsonData = null)
        {
            if (response.IsSuccessStatusCode)
                return;

            string message = String.Empty;

            string errorData = response.Content.ReadAsStringAsync().Result;

            if (response.Content.Headers.ContentType.MediaType.Equals("text/plain"))
            {
                message = errorData;
            }
            else if (response.Content.Headers.ContentType.MediaType.Equals("application/json"))
            {
                JObject jcontent = (JObject)JsonConvert.DeserializeObject(errorData);
                IDictionary<string, JToken> d = jcontent;

                if (d.ContainsKey("error"))
                {
                    JObject error = (JObject)jcontent.Property("error").Value;
                    message = (String)error.Property("message").Value;
                }
                else if (d.ContainsKey("Message"))
                    message = (String)jcontent.Property("Message").Value;


            }
            else if (response.Content.Headers.ContentType.MediaType.Equals("text/html"))
            {
                message = "HTML Error Content:";
                message += "\n\n" + errorData;
            }
            else
            {
                message = String.Format("Error occurred and no handler is available for content in the {0} format.",
                    response.Content.Headers.ContentType.MediaType.ToString());
            }

            var exception = new CRMWebAPIException(message);

            if (jsonData != null)
                exception.JSON = jsonData;

            throw exception;

        }

        /// <summary>
        /// currently the content type for individual responses is missing msgtype=response that the API needs to parse it
        /// </summary>
        /// <param name="changesetStream"></param>
        /// <returns></returns>
        private static HttpContent FixupToAddCorrectHttpContentType(HttpContent changesetStream)
        {
            changesetStream.Headers.Remove("Content-Type");
            changesetStream.Headers.Add("Content-Type", "application/http; msgtype=response");

            return changesetStream;

        }
        /// <summary>
        /// currently the change set is missing a new line at the end - this fixes that
        /// </summary>
        /// <param name="changesetStream"></param>
        /// <returns></returns>
        private static StreamContent FixupChangeStreamDueToBug(HttpContent changesetStream)
        {
            Stream changesetResultStream = changesetStream.ReadAsStreamAsync().Result;
            MemoryStream tempStream = new MemoryStream();
            changesetResultStream.CopyTo(tempStream);

            tempStream.Seek(0, SeekOrigin.End);
            StreamWriter writer = new StreamWriter(tempStream);
            //add new line to fix stream
            writer.WriteLine();
            writer.Flush();
            tempStream.Position = 0;

            StreamContent changesetFixedContent = new StreamContent(tempStream);
            foreach (var header in changesetStream.Headers)
            {
                changesetFixedContent.Headers.Add(header.Key, header.Value);
            }

            return changesetFixedContent;
        }

    }
}
